import bcrypt from 'bcryptjs'
import crypto from 'crypto'
import jwt from 'jsonwebtoken'
import { logger } from './logger'

// Password security utilities
export class PasswordSecurity {
  private static readonly SALT_ROUNDS = 12
  private static readonly MIN_LENGTH = 8
  private static readonly MAX_LENGTH = 128

  // Hash password with bcrypt
  static async hashPassword(password: string): Promise<string> {
    if (!this.validatePasswordLength(password)) {
      throw new Error('Password length is invalid')
    }
    return bcrypt.hash(password, this.SALT_ROUNDS)
  }

  // Verify password against hash
  static async verifyPassword(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash)
  }

  // Validate password strength
  static validatePasswordStrength(password: string, level: 'basic' | 'medium' | 'strong' = 'medium'): {
    valid: boolean
    errors: string[]
  } {
    const errors: string[] = []

    if (!this.validatePasswordLength(password)) {
      errors.push(`Password must be between ${this.MIN_LENGTH} and ${this.MAX_LENGTH} characters`)
    }

    switch (level) {
      case 'basic':
        // Just length requirement
        break
      
      case 'medium':
        if (!/(?=.*[a-z])/.test(password)) {
          errors.push('Password must contain at least one lowercase letter')
        }
        if (!/(?=.*[A-Z])/.test(password)) {
          errors.push('Password must contain at least one uppercase letter')
        }
        if (!/(?=.*\d)/.test(password)) {
          errors.push('Password must contain at least one number')
        }
        break
      
      case 'strong':
        if (!/(?=.*[a-z])/.test(password)) {
          errors.push('Password must contain at least one lowercase letter')
        }
        if (!/(?=.*[A-Z])/.test(password)) {
          errors.push('Password must contain at least one uppercase letter')
        }
        if (!/(?=.*\d)/.test(password)) {
          errors.push('Password must contain at least one number')
        }
        if (!/(?=.*[@$!%*?&])/.test(password)) {
          errors.push('Password must contain at least one special character (@$!%*?&)')
        }
        if (password.length < 12) {
          errors.push('Password must be at least 12 characters for strong security')
        }
        break
    }

    return {
      valid: errors.length === 0,
      errors
    }
  }

  private static validatePasswordLength(password: string): boolean {
    return password.length >= this.MIN_LENGTH && password.length <= this.MAX_LENGTH
  }
}

// Token and session security
export class TokenSecurity {
  private static readonly JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret-change-in-production'

  // Generate secure random token
  static generateSecureToken(length: number = 32): string {
    return crypto.randomBytes(length).toString('hex')
  }

  // Generate API key
  static generateApiKey(): string {
    const prefix = 'ak_'
    const randomPart = this.generateSecureToken(24)
    return prefix + randomPart
  }

  // Create JWT token
  static createJWT(payload: object, expiresIn: string = '1h'): string {
    return jwt.sign(payload, this.JWT_SECRET, { 
      expiresIn,
      issuer: 'arxiv-subscription-platform',
      audience: 'arxiv-users'
    })
  }

  // Verify JWT token
  static verifyJWT(token: string): any {
    try {
      return jwt.verify(token, this.JWT_SECRET, {
        issuer: 'arxiv-subscription-platform',
        audience: 'arxiv-users'
      })
    } catch (error) {
      logger.warn('JWT verification failed', { error: error instanceof Error ? error.message : String(error) })
      return null
    }
  }

  // Generate email verification token
  static generateEmailVerificationToken(userId: string, email: string): string {
    const payload = {
      userId,
      email,
      type: 'email_verification',
      timestamp: Date.now()
    }
    return this.createJWT(payload, '24h')
  }

  // Generate password reset token
  static generatePasswordResetToken(userId: string, email: string): string {
    const payload = {
      userId,
      email,
      type: 'password_reset',
      timestamp: Date.now()
    }
    return this.createJWT(payload, '1h')
  }

  // Verify email verification token
  static verifyEmailVerificationToken(token: string): { userId: string; email: string } | null {
    const decoded = this.verifyJWT(token)
    if (decoded && decoded.type === 'email_verification') {
      return { userId: decoded.userId, email: decoded.email }
    }
    return null
  }

  // Verify password reset token
  static verifyPasswordResetToken(token: string): { userId: string; email: string } | null {
    const decoded = this.verifyJWT(token)
    if (decoded && decoded.type === 'password_reset') {
      return { userId: decoded.userId, email: decoded.email }
    }
    return null
  }
}

// Input sanitization and validation
export class InputSecurity {
  // Sanitize string input
  static sanitizeString(input: string): string {
    return input
      .trim()
      .replace(/[<>]/g, '') // Remove potential HTML tags
      .substring(0, 1000) // Limit length
  }

  // Validate email format
  static validateEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    return emailRegex.test(email) && email.length <= 320 // RFC 5321 limit
  }

  // Validate phone number (basic)
  static validatePhoneNumber(phone: string): boolean {
    const phoneRegex = /^\+?[\d\s\-\(\)]{7,15}$/
    return phoneRegex.test(phone)
  }

  // Validate URL
  static validateUrl(url: string): boolean {
    try {
      const urlObj = new URL(url)
      return urlObj.protocol === 'http:' || urlObj.protocol === 'https:'
    } catch {
      return false
    }
  }

  // Remove potentially dangerous characters from user input
  static sanitizeForDatabase(input: any): any {
    if (typeof input === 'string') {
      return this.sanitizeString(input)
    }
    if (typeof input === 'object' && input !== null) {
      const sanitized: any = Array.isArray(input) ? [] : {}
      for (const key in input) {
        if (input.hasOwnProperty(key)) {
          sanitized[key] = this.sanitizeForDatabase(input[key])
        }
      }
      return sanitized
    }
    return input
  }
}

// Rate limiting utilities
export class RateLimitSecurity {
  private static attempts: Map<string, { count: number; lastAttempt: number }> = new Map()
  private static readonly CLEANUP_INTERVAL = 60000 // 1 minute
  private static cleanupTimer: NodeJS.Timeout | null = null

  // Check if IP/user is rate limited
  static checkRateLimit(
    identifier: string, 
    maxAttempts: number = 5, 
    windowMs: number = 900000 // 15 minutes
  ): { allowed: boolean; remainingAttempts: number; resetTime: number } {
    const now = Date.now()
    const record = this.attempts.get(identifier)

    if (!record || (now - record.lastAttempt) > windowMs) {
      // Reset or create new record
      this.attempts.set(identifier, { count: 1, lastAttempt: now })
      return {
        allowed: true,
        remainingAttempts: maxAttempts - 1,
        resetTime: now + windowMs
      }
    }

    if (record.count >= maxAttempts) {
      return {
        allowed: false,
        remainingAttempts: 0,
        resetTime: record.lastAttempt + windowMs
      }
    }

    // Increment attempt count
    record.count++
    record.lastAttempt = now
    this.attempts.set(identifier, record)

    return {
      allowed: true,
      remainingAttempts: maxAttempts - record.count,
      resetTime: record.lastAttempt + windowMs
    }
  }

  // Reset rate limit for identifier
  static resetRateLimit(identifier: string): void {
    this.attempts.delete(identifier)
  }

  // Start cleanup timer
  static startCleanup(): void {
    if (this.cleanupTimer) return

    this.cleanupTimer = setInterval(() => {
      const now = Date.now()
      const expiredKeys: string[] = []

      for (const [key, record] of this.attempts.entries()) {
        if ((now - record.lastAttempt) > 900000) { // 15 minutes
          expiredKeys.push(key)
        }
      }

      expiredKeys.forEach(key => this.attempts.delete(key))
      
      if (expiredKeys.length > 0) {
        logger.debug('Cleaned up rate limit records', { count: expiredKeys.length })
      }
    }, this.CLEANUP_INTERVAL)
  }

  // Stop cleanup timer
  static stopCleanup(): void {
    if (this.cleanupTimer) {
      clearInterval(this.cleanupTimer)
      this.cleanupTimer = null
    }
  }
}

// GDPR compliance utilities
export class GDPRCompliance {
  // Generate data export for user
  static async generateDataExport(userData: any): Promise<{
    data: any
    metadata: {
      exportDate: string
      format: string
      retention: string
    }
  }> {
    return {
      data: this.sanitizeForExport(userData),
      metadata: {
        exportDate: new Date().toISOString(),
        format: 'JSON',
        retention: 'Data will be retained according to our Privacy Policy'
      }
    }
  }

  // Sanitize data for export (remove sensitive fields)
  private static sanitizeForExport(data: any): any {
    if (typeof data !== 'object' || data === null) {
      return data
    }

    const sensitiveFields = [
      'password',
      'password_hash',
      'session_token',
      'api_key',
      'stripe_customer_id',
      'payment_method_id'
    ]

    const sanitized = Array.isArray(data) ? [] : {}

    for (const key in data) {
      if (data.hasOwnProperty(key)) {
        if (sensitiveFields.includes(key.toLowerCase())) {
          continue // Skip sensitive fields
        }
        
        if (typeof data[key] === 'object') {
          sanitized[key] = this.sanitizeForExport(data[key])
        } else {
          sanitized[key] = data[key]
        }
      }
    }

    return sanitized
  }

  // Validate GDPR consent
  static validateGDPRConsent(consent: any): { valid: boolean; errors: string[] } {
    const errors: string[] = []

    if (typeof consent !== 'object' || consent === null) {
      errors.push('Consent must be an object')
      return { valid: false, errors }
    }

    if (typeof consent.gdpr_consent !== 'boolean') {
      errors.push('GDPR consent must be explicitly granted (true/false)')
    }

    if (consent.gdpr_consent && !consent.gdpr_consent_date) {
      errors.push('Consent date must be provided when consent is granted')
    }

    return {
      valid: errors.length === 0,
      errors
    }
  }
}

// Initialize rate limiting cleanup
RateLimitSecurity.startCleanup()

// Cleanup on process exit
process.on('exit', () => {
  RateLimitSecurity.stopCleanup()
})